package com.fight.strive.sys.modules.scheduler.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fight.strive.sys.modules.common.PageR;
import com.fight.strive.sys.modules.exception.StriveException;
import com.fight.strive.sys.modules.mybatis.MyBatisPlusUtils;
import com.fight.strive.sys.modules.scheduler.Executor;
import com.fight.strive.sys.modules.scheduler.constants.SchConstants;
import com.fight.strive.sys.modules.scheduler.dao.SchJobMapper;
import com.fight.strive.sys.modules.scheduler.entity.SchExecutorEntity;
import com.fight.strive.sys.modules.scheduler.entity.SchJobEntity;
import com.fight.strive.sys.modules.scheduler.service.IStriveJob;
import com.fight.strive.sys.modules.scheduler.service.SchExecutorService;
import com.fight.strive.sys.modules.scheduler.service.SchJobService;
import com.fight.strive.sys.modules.validator.StriveValid;
import com.fight.strive.sys.utils.CollectionUtils;
import com.fight.strive.sys.utils.CronUtils;
import com.fight.strive.sys.utils.ObjectUtils;
import com.fight.strive.sys.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.ReferenceConfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@DS("master")
public class SchJobServiceImpl
        extends ServiceImpl<SchJobMapper, SchJobEntity>
        implements SchJobService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    @Lazy
    private Executor executor;

    @Resource
    @Lazy
    private SchExecutorService schExecutorService;

    static final DateTimeFormatter formatter =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    @Lock4j
    public void handleJob() {
        // 调度器运行标记，如果调度器超过2分钟未执行，
        // 则 redis中的key:"key_expiration_event:scheduler_running" 会触发过期事件
        redisTemplate.opsForValue().set(
                "key_expiration_event:scheduler_running",
                "running", 120, TimeUnit.SECONDS);
        // 获取可运行的任务列表
        List<SchJobEntity> jobList = this.getRunnableJob();
        if (CollectionUtils.isNotEmpty(jobList)) {
            for (SchJobEntity entity : jobList) {
                LocalDateTime nextTime = this.getNextTime(entity);
                if (nextTime.isBefore(LocalDateTime.now())) {
                    // 执行任务
                    executor.execute(entity);
                    // 更新下次执行时间
                    nextTime = CronUtils.getNextTime(entity.getCron());
                    redisTemplate.opsForValue()
                            .set("t_sys_sch_job:next_time:"
                                            .concat(String.valueOf(entity.getId())),
                                    nextTime.format(formatter),
                                    10, TimeUnit.MINUTES);
                }
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<SchJobEntity> getRunnableJob() {
        List<SchJobEntity> jobList = (List<SchJobEntity>) redisTemplate
                .opsForValue().get("t_sys_sch_job:runnable_jobs");
        if (ObjectUtils.isNull(jobList)) {
            QueryWrapper<SchJobEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("status", SchConstants.RUNNING);
            jobList = this.list(queryWrapper);
            redisTemplate.opsForValue().set(
                    "t_sys_sch_job:runnable_jobs", jobList);
        }
        return jobList;
    }

    @Override
    public void saveE(@StriveValid SchJobEntity entity) {
        SchExecutorEntity exec =
                schExecutorService.getById(entity.getExecId());
        if (ObjectUtils.isNull(exec)) {
            throw new StriveException("无法获取远程服务");
        }
        entity.setExecCode(exec.getCode())
                .setExecName(exec.getName());
        if (StringUtils.isBlank(entity.getStatus())) {
            entity.setStatus(SchConstants.STOPPED);
        }
        CronUtils.validCron(entity.getCron());
        try {
            this.saveOrUpdate(entity);
            // 删除缓存
            redisTemplate.delete("t_sys_sch_job:runnable_jobs");
            // 删除下一次执行时间
            redisTemplate.delete(
                    "t_sys_sch_job:next_time:".concat(String.valueOf(entity.getId())));
        } catch (DuplicateKeyException e) {
            throw new StriveException("该任务编号已存在");
        }
    }

    @Override
    public void deleteJob(SchJobEntity entity) {
        redisTemplate.delete("t_sys_sch_job:runnable_jobs");
        this.removeById(entity.getId());
    }

    @Override
    public IPage<SchJobEntity> list(PageR<SchJobEntity> page) {
        QueryWrapper<SchJobEntity> queryWrapper = new QueryWrapper<>();
        MyBatisPlusUtils.buildAndQuery(queryWrapper, page.getCond());
        return this.page(page, queryWrapper);
    }

    @Override
    public void changeStatus(SchJobEntity entity) {
        redisTemplate.delete("t_sys_sch_job:runnable_jobs");
        this.updateById(entity);
    }

    @Override
    public void runJobOnce(SchJobEntity entity) {
        entity = this.getById(entity.getId());
        executor.execute(entity);
    }

    @Override
    public String getServiceUrl(SchJobEntity entity) {
        String prefix = schExecutorService.getRobinUrl(entity.getExecId());
        if (StringUtils.isNotBlank(prefix)) {
            return prefix.concat(entity.getPath());
        } else {
            throw new StriveException("无法获取服务地址");
        }
    }

    @Override
    public LocalDateTime getNextTime(SchJobEntity entity) {
        LocalDateTime nextTime;
        String nextTimeStr = (String) redisTemplate
                .opsForValue().get("t_sys_sch_job:next_time:"
                        .concat(String.valueOf(entity.getId())));
        if (ObjectUtils.isNull(nextTimeStr)) {
            nextTime = CronUtils.getNextTime(entity.getCron());
        } else {
            nextTime = LocalDateTime.parse(nextTimeStr, formatter);
        }
        redisTemplate.opsForValue().set("t_sys_sch_job:next_time:"
                        .concat(String.valueOf(entity.getId())), nextTime.format(formatter),
                10, TimeUnit.MINUTES);
        return nextTime;
    }

    @Override
    public boolean executorHasJob(Long execId) {
        QueryWrapper<SchJobEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("exec_id", execId);
        return this.count(queryWrapper) > 0;
    }

    @Override
    public void executeJob(SchJobEntity entity) {
        ReferenceConfig<IStriveJob> reference = new ReferenceConfig<>();
        reference.setUrl(this.getServiceUrl(entity));
        reference.setInterface(IStriveJob.class);
        // 超时时间设置为5小时
        reference.setTimeout(1000 * 3600 * 5);
        IStriveJob job = reference.get();
        job.handler(entity.getParams());
    }
}
